package com.yunxin.download.server.infra.support.mybatis;

import com.yunxin.common.exception.BusinessException;
import com.yunxin.download.server.infra.constant.DownloadCommonConstant;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.scripting.xmltags.XMLLanguageDriver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.HashMap;

/**
 * @author zero°
 * @since 2023-06-15
 */
@Slf4j
@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class,
                Object.class,
                RowBounds.class,
                ResultHandler.class})})
public class DownloadDaoInterceptor implements Interceptor {

    /**
     * 拦截dao的方法名
     */
    private static final String FILTER_METHOD = "com.yunxin.download.server.infra.repository.DownloadDao.download";

    public DownloadDaoInterceptor() {
        log.warn("初始化中...........");
    }

    /**
     * intercept 方法用来对拦截的 sql 进行具体的操作
     * 本拦截方法只对 mapper 接口中的 searchByQuery 方法进行拦截，实际每个方法都拦截了，
     * 只是只有 searchByQuery 方法时，才真正执行 拦截的相关操作
     * @param invocation 拦截器执行器
     *
     * @return Object
     *
     * @throws Throwable 异常信息
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //  获取 invocation 传递的参数
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        // 获取执行的该拦截器的全路径方法 比如你的 UserInfoMapper 接口的 getById 方法， com.xx.UserInfoMapper.getById
        String id = ms.getId();
        Configuration configuration = ms.getConfiguration();
        // 如果不是 searchByQuery 方法，就不进行相关的拦截操作
        if (!filterMethodById(id)) {
            return invocation.proceed();
        }
        // 该参数类型 org.apache.ibatis.binding.MapperMethod$ParamMap
        Object parameterObject = args[1];

        // 获取传递的参数， 主要包含三个参数 1. 查询语句 searchQuery， 2. 查询偏移 pageOffset, 3. 查询每页的数据条数
        HashMap<String, Object> paramMap = (HashMap<String, Object>) parameterObject;
        Object sqlContentObj = paramMap.get(DownloadCommonConstant.SQL_CONTENT);
        if (sqlContentObj == null) {
            throw new BusinessException("没有sql字符串 无法拼接sql");
        }
        String sqlContent = sqlContentObj.toString();

        XMLLanguageDriver xmlLanguageDriver = new XMLLanguageDriver();
        SqlSource sqlSource = xmlLanguageDriver.createSqlSource(configuration, sqlContent, HashMap.class);
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);

        //第一步  编译文本内容
        final String boundSqlSql = boundSql.getSql();

        // 重新new一个查询语句对象
        BoundSql newBoundSql = new BoundSql(ms.getConfiguration(), boundSqlSql,
                boundSql.getParameterMappings(), boundSql.getParameterObject());

        // 把新的查询放到statement里
        MappedStatement newMs = newMappedStatement(ms, new BoundSqlSqlSource(newBoundSql));

        for (ParameterMapping mapping : boundSql.getParameterMappings()) {
            String prop = mapping.getProperty();
            if (boundSql.hasAdditionalParameter(prop)) {
                newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));
            }
        }

        Object[] queryArgs = invocation.getArgs();
        queryArgs[0] = newMs;

        return invocation.proceed();
    }

    /**
     * 定义一个内部辅助类，作用是包装 SQL
     */
    static class BoundSqlSqlSource implements SqlSource {
        private final BoundSql boundSql;

        public BoundSqlSqlSource(BoundSql boundSql) {
            this.boundSql = boundSql;
        }

        @Override
        public BoundSql getBoundSql(Object parameterObject) {
            return boundSql;
        }

    }

    private MappedStatement newMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
        MappedStatement.Builder builder = new
                MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource, ms.getSqlCommandType());
        builder.resource(ms.getResource());
        builder.fetchSize(ms.getFetchSize());
        builder.statementType(ms.getStatementType());
        builder.keyGenerator(ms.getKeyGenerator());
        if (ms.getKeyProperties() != null && ms.getKeyProperties().length > 0) {
            builder.keyProperty(ms.getKeyProperties()[0]);
        }
        builder.timeout(ms.getTimeout());
        builder.parameterMap(ms.getParameterMap());
        builder.resultMaps(ms.getResultMaps());
        builder.resultSetType(ms.getResultSetType());
        builder.cache(ms.getCache());
        builder.flushCacheRequired(ms.isFlushCacheRequired());
        builder.useCache(ms.isUseCache());
        return builder.build();
    }

    /**
     * @param id id
     *
     * @return boolean
     */
    private boolean filterMethodById(String id) {
        return FILTER_METHOD.equals(id);
    }


    /***
     * 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
     * @param target 需要拦截的类
     * @return Object
     */
    @Override
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        }
        return target;

    }


}
